using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.IO;
using System.Data.Common;
using System.Data;
using Yogesh.ExcelXml;
using ICSharpCode.SharpZipLib.Zip;
using System.Text;
using System.Collections;
using System.Xml;
using System.Xml.XPath;
using System.Threading;
using System.Text.RegularExpressions;
//using System.Data.SQLite;

/// <summary>
/// Imports from an Excel XML spreadsheet
/// </summary>
public partial class Import : System.Web.UI.Page
{
    private Database db;

    private enum FileType {
        XML,
        Properties,
        Resx,
        Firefox,
        PO
    }

    /// <summary>
    /// Clears errors from previous submissions
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
    protected void Page_Load(object sender, EventArgs e)
    {
        if(!Page.IsPostBack) {
            if(db == null || !db.Connected) {
                db = new Database();
                db.OpenConnection();
            }
            using(Database.Command cmd = db.CreateCommand()) {
                cmd.CommandText = "SELECT id, displayname FROM " + db.TablePrefix + "applications ORDER BY displayname ASC";
                Database.DataAdapter adapter = new Database.DataAdapter(cmd);
                DataSet ds = new DataSet();
                adapter.Fill(ds);
                radApplication.DataSource = ds;
                radApplication.DataValueField = "id";
                radApplication.DataTextField = "displayname";
                radApplication.DataBind();
            }
            if(radApplication.Items.Count > 0) {
                radApplication.SelectedIndex = 0;
                mltImport.ActiveViewIndex = 1;
            }
        }
    }
    
    /// <summary>
    /// Uploads the file and begins importing. If the file is a zip file, all files are extracted from it before continuing.
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
    protected void btnUpload_Click(object sender, EventArgs e) {
        if(uplXlsFile.HasFile) {
            try {
                string ext = Path.GetExtension(uplXlsFile.FileName).ToLowerInvariant().Substring(1); // the extension of the uploaded file (could be zip)
                string innerExtension = string.Empty; // the file within the zip
                Dictionary<string, string> files = new Dictionary<string, string>();
                StringBuilder fileBuffer = new StringBuilder();
                using(MemoryStream ms = new MemoryStream()) {
                    if(ext == "zip") {
                        ZipInputStream zipStream = new ZipInputStream(uplXlsFile.FileContent);
                        ZipEntry entry;
                        byte[] buffer = new byte[1024];
                        while((entry = zipStream.GetNextEntry()) != null) {
                            fileBuffer.Remove(0, fileBuffer.Length);
                            if(innerExtension == string.Empty) {
                                if(radFiletype.SelectedValue == "auto") {
                                    string detectedExt = Path.GetExtension(entry.Name);
                                    if(detectedExt.Length > 1) {
                                        innerExtension = detectedExt.ToLowerInvariant().Substring(1); // use the extension of the first file in the archive
                                    }
                                } else {
                                    innerExtension = radFiletype.SelectedValue;
                                }
                            }
                            StreamReader reader = new StreamReader(zipStream);
                            files.Add(entry.Name, reader.ReadToEnd());
                        }
                    } else {
                        using(StreamReader reader = new StreamReader(uplXlsFile.FileContent)) {
                            files.Add(uplXlsFile.FileName, reader.ReadToEnd().Replace("\r", ""));
                        }
                        if(radFiletype.SelectedValue == "auto") {
                            innerExtension = Path.GetExtension(uplXlsFile.FileName).ToLowerInvariant().Substring(1);
                        } else {
                            innerExtension = radFiletype.SelectedValue;
                        }
                    }
                }
                if(innerExtension.Length == 0) {
                    throw new Exception("Could not detect file type");
                }
                if(files.Count == 0) {
                    throw new Exception("No files found");
                }
                switch(innerExtension) {
                    case "xml":
                        Session["innerExtension"] = "xml";
                        Session["importFile"] = files;
                        PromptApplication(string.Format("Uploaded XML file. Click 'Import' to start."));
                        pnlPromptApplication.Visible = false;
                        break;
                    case "properties":
                        Session["innerExtension"] = "properties";
                        Session["importFile"] = files;
                        PromptApplication(string.Format("Uploaded <strong>{0}</strong> {1}. Choose an application to which to add {2}, then click 'Import' to start.", files.Count, files.Count != 1 ? "bundles" : "bundle", files.Count == 1 ? "this bundle" : "these bundles"));
                        break;
                    case "resx":
                        Session["innerExtension"] = "resx";
                        Session["importFile"] = files;
                        PromptApplication(string.Format("Uploaded <strong>{0}</strong> {1}. Choose an application to which to add {2}, then click 'Import' to start.", files.Count, files.Count != 1 ? "bundles" : "bundle", files.Count == 1 ? "this bundle" : "these bundles"));
                        break;
                    case "po":// todo: make this parse subdirs for multiple bundles
                        Session["innerExtension"] = "po";
                        Session["importFile"] = files;
                        PromptApplication(string.Format("Uploaded <strong>{0}</strong> {1}. Choose an application to which to add {2}, then click 'Import' to start.", files.Count, files.Count != 1 ? "files" : "file", files.Count == 1 ? "this bundle" : "these bundles"));
                        break;
                    case "rbt":
                        throw new ResourceBlenderException("RBT translation databases are only supported by ResourceBlender Express");
                    default:
                        throw new ResourceBlenderException("Unsupported file type - " + innerExtension);
                }
            } catch(Exception ex) {
                Label lblError = viwFiles.FindControl("lblError") as Label;
                lblError.Visible = true;
                lblError.Text = ex.Message;
            }
        } else {
            Label lblError = viwFiles.FindControl("lblError") as Label;
            lblError.Visible = true;
            lblError.Text = "No file specified";
        }
    }

    /// <summary>
    /// Prompts for the application to which to add the uploaded bundles.
    /// </summary>
    /// <param name="bundles">The uploaded bundles.</param>
    private void PromptApplication(string message){
        mltImport.ActiveViewIndex = 2;
        litUploadedInfo.Text = message;
    }

    /// <summary>
    /// Starts the import process.
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
    protected void btnImport_Click(object sender, EventArgs e) {
        litProgress.Visible = true;
        pnlPromptApplication.Visible = false;
        if(Session["importFile"] == null || Session["importFile"] == null) {
            ListItem itm = new ListItem("Error #409: Could not load file from cache.");
            itm.Attributes.Add("class", "error");
            lstProgress.Items.Add(itm); //todo: check on error
            return;
        }
        lstProgress.Items.Clear();
        Thread worker = new Thread(new ThreadStart(StartImport));
        imgProgress.Visible = true;
        worker.Name = "ImportWorker";
        worker.IsBackground = true;
        worker.Start();
        Session["worker"] = worker;
        List<ListItem> log = new List<ListItem>();
        log.Add(new ListItem("Starting import...")); // the log is deserialized on every timer tick
        Session["log"] = log;
        Session["importDone"] = bool.FalseString;
        tmrImport.Enabled = true;
        btnImport.Enabled = false;
    }

    /// <summary>
    /// Creates a new thread and starts importing.
    /// </summary>
    private void StartImport() {
        try {
            // the class which does the importing is the same in both RB.NET and RB Express. We just need to make a mock RBApplication containing the necessary info
            if(db == null || !db.Connected) {
                db = new Database();
                db.OpenConnection();
            }
            StatusMessage.StatusChangedEventHandler logHandler = new StatusMessage.StatusChangedEventHandler((statusMessage) => {
                LogText(statusMessage.Message, statusMessage.MessageType == StatusMessage.StatusMessageType.Error);
            });
            RBApplication app = new RBApplication();
            using(Database.Command cmd = db.CreateCommand()) {
                string appId = (viwApp.FindControl("radApplication") as RadioButtonList).SelectedValue,
                       defaultLanguage = string.Empty;
                string[] appLanguages = null;
                cmd.CommandText = string.Format("SELECT defaultlanguage, languages FROM {0}applications WHERE id = @id", db.TablePrefix);
                cmd.AddParameterWithValue("@id", appId);
                DbDataReader reader = cmd.ExecuteReader();
                while(reader.Read()) {
                    defaultLanguage = reader["defaultlanguage"].ToString();
                    appLanguages = reader["languages"].ToString().Trim().Split(',');
                }
                reader.Close();
                app.ID = appId;
                app.DefaultLanguage = defaultLanguage;
                app.Languages = appLanguages.ToList();
            }

            Dictionary<string, string> files = Session["importFile"] as Dictionary<string, string>;
            string innerExtension = Session["innerExtension"].ToString();
            switch(innerExtension) {
                case "xml":
                    ResourceImporter.ImportXMLSpreadsheet(logHandler, files.First().Value);
                    break;
                case "properties":
                    ResourceImporter.ImportProperties(logHandler, files, app);
                    break;
                case "resx":
                    ResourceImporter.ImportResx(logHandler, files, app);
                    break;
                case "po":
                    ResourceImporter.ImportPO(logHandler, files, app);
                    break;
            }
            LogText("Import complete.", false);
            Session["importDone"] = bool.TrueString;
        } catch(Exception ex) {
            LogText("Error restoring #406: " + ex.Message, true);
            Session["importDone"] = bool.TrueString;
        }
        Thread worker = Session["worker"] as Thread;
        if(worker != null) {
            worker.Abort();
        }
    }

    /// <summary>
    /// Logs text to the progress BulletedList.
    /// </summary>
    /// <param name="text">The text to log.</param>
    /// <param name="error"><c>true</c> if the 'error' CSS class should be applied to the text.</param>
    private void LogText(string text, bool error) {
        List<ListItem> log = Session["log"] as List<ListItem>;
        ListItem itm = new ListItem(text);
        if(error) {
            itm.Attributes.Add("class", "error");
        }
        log.Add(itm);
    }

    /// <summary>
    /// Deserializes the import log from the Session cache and re-populates the progress BulletedList.
    /// Detects completion of the operation and stops the timer.
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
    protected void tmrImport_Tick(object sender, EventArgs e) {
        try {
            List<ListItem> log = Session["log"] as List<ListItem>;
            lstProgress.Items.Clear();
            lstProgress.Items.AddRange(log.ToArray());
            if(bool.Parse(Session["importDone"].ToString())) {
                (sender as System.Web.UI.Timer).Enabled = false;
                Session.Remove("importFile");
                Session.Remove("importDone");
                imgProgress.Visible = false;
            }
        } catch {
            lstProgress.Items.Add("Error communicating with server, aborting.");
            Session.Remove("importFile");
            Session["importDone"] = bool.TrueString;
        }
    }
}
